package sodb

import (
	"context"
	"io/fs"
	"os"
	"sync"
	"sync/atomic"
	"time"

	"github.com/vmihailenco/msgpack/v5"
)

const (
	saveInterval = time.Millisecond * 500
)

type SoDB struct {
	ctx     context.Context
	path    string // 本地存储路径
	sm      sync.Map
	newflag atomic.Value
}

func NewSoDB(cancelCtx context.Context, path string) (sd *SoDB) {
	sd = &SoDB{
		ctx:  cancelCtx,
		path: path,
	}
	if sd.loadFromLocal() == nil {
		go func() {
			for {
				select {
				case <-sd.ctx.Done():
					return
				default:
					if new := sd.newflag.Load(); new == nil || new.(bool) {
						sd.newflag.Store(false)
						sd.saveToLocal()
					}
					time.Sleep(saveInterval)
				}
			}
		}()
	}
	return
}

func (sd *SoDB) loadFromLocal() error {
	if _, e := os.Stat(sd.path); os.IsNotExist(e) {
		return nil
	}

	data, e := os.ReadFile(sd.path)
	if e != nil {
		return e
	}

	mapdata := map[string][]byte{}
	if e = msgpack.Unmarshal(data, &mapdata); e != nil {
		return e
	}

	for k, v := range mapdata {
		sd.sm.Store(k, v)
	}

	return nil
}

func (sd *SoDB) saveToLocal() error {
	mapdata := map[string][]byte{}
	counter := 0
	sd.sm.Range(func(k any, v any) bool {
		mapdata[k.(string)] = v.([]byte)
		counter++
		return true
	})

	if counter > 0 {
		bts, _ := msgpack.Marshal(&mapdata)
		return os.WriteFile(sd.path, bts, fs.ModePerm)
	}
	return nil
}

// Set value
func (sd *SoDB) Set(k string, v []byte) {
	sd.sm.Store(k, v)
	sd.newflag.Store(true)
}

// Get key value
func (sd *SoDB) Get(k string) ([]byte, bool) {
	v, ok := sd.sm.Load(k)
	return v.([]byte), ok
}

// Range each item
func (sd *SoDB) Range(f func(k string, v []byte) bool) {
	sd.sm.Range(func(key, value any) bool {
		return f(key.(string), value.([]byte))
	})
}
